package at.jaki.ubb.crawling.dataFinder;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.Future;

import org.apache.log4j.Logger;

import at.jaki.ubb.crawling.api.DataFinder;
import at.jaki.ubb.crawling.appThreads.DataFinderResultManager;
import at.jaki.ubb.crawling.core.Constants;
import at.jaki.ubb.crawling.core.ThreadPool;
import at.jaki.ubb.crawling.model.Target;
import at.jaki.ubb.crawling.model.config.DataFinderConfig;
import at.jaki.ubb.crawling.model.config.DataFinderManagerConfig;

/**
 * This class manages all the data finding operations. used to call all the
 * datafinders.
 * 
 * @author Attila Jakabfi
 * 
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public final class DataFinderManager {

	private static final Logger LOG = Logger.getLogger(DataFinderManager.class);

	private static DataFinderManager instance;

	/**
	 * Initializes the {@link DataFinderManager}.
	 * 
	 * @param configObject .
	 * @return true if initialization was successful
	 */
	public static boolean init(DataFinderManagerConfig configObject) {
		instance = new DataFinderManager(configObject);
		return instance != null;
	}

	public static DataFinderManager getInstance() {
		if (instance == null) {
			throw new IllegalStateException("DataFinderManager is not initialized.");
		}
		return instance;
	}

	/**
	 * Map containing all started data finder threads.
	 */
	private Map<String, Collection<DataFinderThread>> threadMap = new HashMap<String, Collection<DataFinderThread>>();

	/**
	 * @param configObject the {@link DataFinderManagerConfig} object.
	 *        Containing all the data require for initialization.
	 */
	private DataFinderManager(DataFinderManagerConfig configObject) {
		for (DataFinderConfig instance : configObject.getDataFinders()) {
			DataFinderThread dft = new DataFinderThread(instance.getClazzName());
			dft.setName(instance.getLogName());
			if (dft.isInitialized()) {
				Collection<String> typesList = dft.getTypes();
				for (String type : typesList) {
					if (!threadMap.containsKey(type)) {
						threadMap.put(type, new LinkedList<DataFinderThread>());
					}
					threadMap.get(type).add(dft);
				}
			}
		}
	}

	/**
	 * Will find a {@link DataFinder} of type <code>type</code> and execute it
	 * for <code>target</code>.
	 * 
	 * @param t target
	 */
	public synchronized <E> void call(Target<E> t) {
		LOG.debug("Called DataFinderManager for type '" + t.getType() + "' and target '" + t.toString() + "'");
		if (!threadMap.containsKey(t.getType())) {
			LOG.warn("No data finder registered for type: " + t.getType());
			LOG.debug("Processing of " + t.toString() + " skipped");
			return;
		}
		Collection<DataFinderThread> dataFinders = threadMap.get(t.getType());
		outer: while (true) {
			for (DataFinderThread thread : dataFinders) {
				if (!thread.isBusy()) {
					thread.setTarget(t);
					LOG.debug("submitting task '" + t + "' to '" + thread.getName() + "'");
					try {
						Future future = ThreadPool.getInstance().submit(thread);
						DataFinderResultManager.getInstance().addTask(future);
						LOG.debug("'" + thread.getName() + "' has been called");
					} catch (Exception e) {
						LOG.error("Error with data finder: " + thread.getName());
						LOG.error("Error while executing task. Stack trace:");
						if (LOG.isDebugEnabled()) {
							e.printStackTrace();
						}
					}
					break outer;
				}
			}
			try {
				Thread.sleep(Constants.SLEEP_TIME);
			} catch (InterruptedException e) {
				LOG.warn("sleep interrupted " + e.getMessage());
			}
		}
	}

}
